﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Framework.Core.Dependency.Domain;

namespace Framework.Core.Dependency.Parser
{
    internal class DotNetProjectParser : IStreamParser<Project>
    {
        private readonly IList<IDotNetProjectParser> parsers = new List<IDotNetProjectParser>
                                                                   {
                                                                       new LegacyVbProjParser(),
                                                                       new LegacyCsProjParser(),
                                                                       new CsProjParser(),
                                                                       new VbProjParser()
                                                                   };

        private interface IDotNetProjectParser
        {
            bool IsValid(XNamespace ns, XDocument project, string file);
            Project Parse(XNamespace ns, XDocument project, string file);
        }

        private class LegacyVbProjParser : IDotNetProjectParser
        {
            public bool IsValid(XNamespace ns, XDocument project, string file)
            {
                return project.Descendants(ns + "VisualBasic").Any();
            }

            public Project Parse(XNamespace ns, XDocument project, string file)
            {
                return ParseLegacyDotNetProject(ns, project, file, Language.Vb);
            }
        }

        private class LegacyCsProjParser : IDotNetProjectParser
        {
            public bool IsValid(XNamespace ns, XDocument project, string file)
            {
                return project.Descendants(ns + "CSHARP").Any();
            }

            public Project Parse(XNamespace ns, XDocument project, string file)
            {
                return ParseLegacyDotNetProject(ns, project, file, Language.CSharp);
            }
        }

        private class CsProjParser : IDotNetProjectParser
        {
            public bool IsValid(XNamespace ns, XDocument project, string file)
            {
                return file.EndsWith(".csproj") && !string.IsNullOrWhiteSpace(ns.ToString());
            }

            public Project Parse(XNamespace ns, XDocument project, string file)
            {
                return ParseDotNetProject(ns, project, file, Language.CSharp);
            }
        }

        private class VbProjParser : IDotNetProjectParser
        {
            public bool IsValid(XNamespace ns, XDocument project, string file)
            {
                return file.EndsWith(".vbproj") && !string.IsNullOrWhiteSpace(ns.ToString());
            }

            public Project Parse(XNamespace ns, XDocument project, string file)
            {
                return ParseDotNetProject(ns, project, file, Language.Vb);
            }
        }

        private static Project ParseLegacyDotNetProject(XNamespace ns, XContainer project, string file, Language language)
        {
            // ReSharper disable PossibleNullReferenceException
            var assemblyName = project.Descendants(ns + "Settings").First().Attribute("AssemblyName").Value;
            // ReSharper restore PossibleNullReferenceException
            var legacyCsProj = new Project(GetProjectName(file), GetExtension(file), file, assemblyName, language);

            project.Descendants(ns + "References")
                   .Descendants(ns + "Reference")
                   .ToList()
                // ReSharper disable PossibleNullReferenceException
                   .ForEach(r => legacyCsProj.AddModule(new Module(r.Attribute("Name").Value, GetLegacyHintPath(r))));
            // ReSharper restore PossibleNullReferenceException
            project.Descendants(ns + "Files")
                   .Descendants(ns + "File")
                   .ToList()
                // ReSharper disable PossibleNullReferenceException
                   .ForEach(f => legacyCsProj.Files.Add(f.Attribute("RelPath").Value));
            // ReSharper restore PossibleNullReferenceException
            return legacyCsProj;
        }

        private static Project ParseDotNetProject(XNamespace ns, XContainer project, string file, Language language)
        {
            var assemblyName = project.Descendants(ns + "AssemblyName").First().Value;
            var csproj = new Project(GetProjectName(file), GetExtension(file), file, assemblyName, language);

            project.Descendants(ns + "Reference")
                   .ToList()
                   .ForEach(
                       r =>
                           // ReSharper disable PossibleNullReferenceException
                       csproj.AddModule(new Module(CleanModule(r.Attribute("Include").Value.Split(',').First()), GetHintPath(ns, r))));
            project.Descendants(ns + "Compile").ToList().ForEach(f => csproj.Files.Add(f.Attribute("Include").Value));
            // ReSharper restore PossibleNullReferenceException

            return csproj;
        }

        private static string GetLegacyHintPath(XElement r)
        {
            var hintPathAttribute = r.Attribute("HintPath");

            var hintPath = (hintPathAttribute != null) ? hintPathAttribute.Value : string.Empty;
            return hintPath;
        }

        private static string GetHintPath(XNamespace ns, XContainer r)
        {
            var hintPathAttribute = r.Descendants(ns + "HintPath").FirstOrDefault();

            var hintPath = (hintPathAttribute != null) ? hintPathAttribute.Value : string.Empty;
            return hintPath;
        }

        private static string GetExtension(string file)
        {
            return Path.GetExtension(file);
        }

        private static string GetProjectName(string file)
        {
            return Path.GetFileNameWithoutExtension(file);
        }

        public Project Parse(Stream projectStream, string file, string branch)
        {
            using (var reader = XmlReader.Create(projectStream))
            {
                var project = XDocument.Load(reader);

                return parsers.First(parser => parser.IsValid(GetNameSpace(project), project, file)).Parse(GetNameSpace(project), project, file);
            }
        }

        private static string CleanModule(string moduleName)
        {
            return moduleName.Replace(".dll", string.Empty);
        }

        private static XNamespace GetNameSpace(XDocument project)
        {
            // ReSharper disable PossibleNullReferenceException
            var nsAttribute = project.Root.Attribute("xmlns");
            // ReSharper restore PossibleNullReferenceException

            if (nsAttribute == null)
            {
                return string.Empty;
            }

            var ns = XNamespace.Get(nsAttribute.Value);
            return ns;
        }
    }
}